Introduction

Build Website Website Website Website

Introduction

The aim of this study is to explore the factor structure of HRV indices.

Databases

Glasgow University Database

The GUDB Database (howell2018high?) contains ECGs from 25 subjects. Each subject was recorded performing 5 different tasks for two minutes (sitting, doing a maths test on a tablet, walking on a treadmill, running on a treadmill, using a hand bike). The sampling rate is 250Hz for all the conditions.

The script to download and format the database using the ECG-GUDB Python package by Bernd Porr can be found here.

MIT-BIH Arrhythmia Database

The MIT-BIH Arrhythmia Database [MIT-Arrhythmia; (moody2001impact?)] contains 48 excerpts of 30-min of two-channel ambulatory ECG recordings sampled at 360Hz and 25 additional recordings from the same participants including common but clinically significant arrhythmias (denoted as the MIT-Arrhythmia-x database).

The script to download and format the database using the can be found here.

MIT-BIH Normal Sinus Rhythm Database

This database includes 18 clean long-term ECG recordings of subjects. Due to memory limits, we only kept the second hour of recording of each participant.

The script to download and format the database using the can be found here.

Fantasia Database

The Fantasia database (iyengar1996age?) consists of twenty young and twenty elderly healthy subjects. All subjects remained in a resting state in sinus rhythm while watching the movie Fantasia (Disney, 1940) to help maintain wakefulness. The continuous ECG signals were digitized at 250 Hz. Each heartbeat was annotated using an automated arrhythmia detection algorithm, and each beat annotation was verified by visual inspection.

Procedure

Results

library(tidyverse)
library(easystats)

data <- read.csv("data/data.csv", stringsAsFactors = FALSE) %>% 
  select(-HRV_ULF, -HRV_VLF) %>%  # Empty
  filter(Database != "LUDB") # too short recordings, many indices didn't converge
names(data) <- stringr::str_remove(names(data), "HRV_")

Redundant Indices

Remove Equivalent (r higher than .995)

data %>% 
  correlation::correlation() %>% 
  filter(abs(r) > 0.995) %>% 
  arrange(Parameter1, desc(abs(r)))
Parameter1 Parameter2 r CI_low CI_high t df p Method n_Obs
C1d C1a -1 -1 -1 -1.1e+09 250 0 Pearson 252
C2d C2a -1 -1 -1 -Inf 250 0 Pearson 252
Cd Ca -1 -1 -1 -Inf 250 0 Pearson 252
RMSSD SDSD 1 1 1 5.0e+04 250 0 Pearson 252
RMSSD SD1 1 1 1 5.0e+04 250 0 Pearson 252
RMSSD SD1d 1 1 1 5.4e+02 250 0 Pearson 252
RMSSD SD1a 1 1 1 4.7e+02 250 0 Pearson 252
SD1 SD1d 1 1 1 5.4e+02 250 0 Pearson 252
SD1 SD1a 1 1 1 4.7e+02 250 0 Pearson 252
SD1d SD1a 1 1 1 2.5e+02 250 0 Pearson 252
SD2 SD2a 1 1 1 2.9e+02 250 0 Pearson 252
SD2 SD2d 1 1 1 2.0e+02 250 0 Pearson 252
SDNN SDNNa 1 1 1 7.3e+02 250 0 Pearson 252
SDNN SDNNd 1 1 1 5.8e+02 250 0 Pearson 252
SDNNd SDNNa 1 1 1 3.2e+02 250 0 Pearson 252
SDSD SD1 1 1 1 Inf 250 0 Pearson 252
SDSD SD1d 1 1 1 5.4e+02 250 0 Pearson 252
SDSD SD1a 1 1 1 4.7e+02 250 0 Pearson 252
data <- data %>% 
  select(-SDSD, -SD1, -SD1d, -SD1a, -CVSD) %>%  # Same as RMSSD 
  select(-SDNNd, -SDNNa) %>%  # Same as SDNN
  select(-SD2d, -SD2a) %>%   # Same as SD2
  select(-Cd) %>%   # Same as Ca
  select(-C1d, -C2d) # Same as C1a and C2a

Remove Strongly Correlated (r higher than .98)

data %>% 
  correlation::correlation() %>% 
  filter(abs(r) > 0.95) %>%
  arrange(Parameter1, desc(abs(r)))
Parameter1 Parameter2 r CI_low CI_high t df p Method n_Obs
CVNN SD2 0.97 0.96 0.98 65 250 0 Pearson 252
GI AI 0.99 0.99 0.99 138 250 0 Pearson 252
GI SI 0.99 0.99 0.99 115 250 0 Pearson 252
MeanNN MedianNN 0.99 0.98 0.99 99 250 0 Pearson 252
PIP IALS 0.98 0.98 0.99 87 250 0 Pearson 252
RMSSD SDNN 0.98 0.98 0.99 80 250 0 Pearson 252
RMSSD CVNN 0.97 0.96 0.98 63 250 0 Pearson 252
SDNN SD2 0.99 0.99 0.99 120 250 0 Pearson 252
SDNN CVNN 0.98 0.98 0.99 89 250 0 Pearson 252
SI AI 0.97 0.96 0.98 63 250 0 Pearson 252
TINN S 0.95 0.94 0.96 51 250 0 Pearson 252
data <- data %>% 
  select(-GI, -SI) %>%  # Same as AI 
  select(-SD2) %>%  # Same as SDNN
  select(-MedianNN) %>%  # Same as MeanNN
  select(-IALS) %>%  # Same as PIP
  select(-SDNN, -CVNN) # Same as RMSSD

Recording Length

Investigate effect

correlation(data) %>% 
  filter(Parameter2 == "Recording_Length") %>% 
  arrange(desc(abs(r)))

Adjust the data for recording length

data <- effectsize::adjust(data, effect="Recording_Length") %>% 
  select(-Recording_Length)

Gaussian Graphical Model

library(ggraph)

data %>% 
  correlation::correlation(partial=FALSE) %>% 
  correlation::cor_to_pcor() %>% 
  filter(abs(r) > 0.2) %>%
  tidygraph::as_tbl_graph(directed=FALSE) %>% 
  dplyr::mutate(closeness = tidygraph::centrality_closeness(normalized = TRUE),
                degree = tidygraph::centrality_degree(normalized = TRUE),
                betweeness = tidygraph::centrality_betweenness(normalized = TRUE)) %>%
  tidygraph::activate(nodes) %>%
  dplyr::mutate(group1 = as.factor(tidygraph::group_edge_betweenness()),
                # group2 = as.factor(tidygraph::group_optimal()),
                # group3 = as.factor(tidygraph::group_walktrap()),
                # group4 = as.factor(tidygraph::group_spinglass()),
                group5 = as.factor(tidygraph::group_louvain())) %>% 
  ggraph::ggraph(layout = "fr") +
    ggraph::geom_edge_arc(aes(colour = r, edge_width = abs(r)), strength = 0.1, show.legend = FALSE) +
    ggraph::geom_node_point(aes(size = degree, color = group5), show.legend = FALSE) +
    ggraph::geom_node_text(aes(label = name), colour = "white") +
    ggraph::scale_edge_color_gradient2(low = "#a20025", high = "#008a00", name = "r") +
    ggraph::theme_graph() +
    guides(edge_width = FALSE) +
    scale_x_continuous(expand = expansion(c(.10, .10))) +
    scale_y_continuous(expand = expansion(c(.10, .10))) +
    scale_size_continuous(range = c(20, 30)) +
    scale_edge_width_continuous(range = c(0.5, 2)) +
    see::scale_color_material_d(palette="rainbow", reverse=TRUE)

Groups were identified using the tidygraph::group_optimal algorithm.

Factor Analysis

How many factors

cor <- correlation::correlation(data[sapply(data, is.numeric)]) %>% 
  as.matrix()

n <- parameters::n_factors(data, cor=cor)

n
n_Factors Method Family
1 t Multiple_regression
1 p Multiple_regression
2 Acceleration factor Scree
2 RMSEA Fit
3 CNG CNG
4 beta Multiple_regression
7 R2 Scree_SE
8 Optimal coordinates Scree
8 Parallel analysis Scree
8 Kaiser criterion Scree
15 SE Scree Scree_SE
21 BIC Fit
27 CRMS Fit
28 TLI Fit
29 Bentler Bentler
35 Bartlett Barlett
35 Anderson Barlett
35 Lawley Barlett
plot(n) +
  see::theme_modern()

Exploratory Factor Analysis (EFA)

efa <- parameters::factor_analysis(data, cor=cor, n=7, rotation="varimax", fm="ml")

print(efa, threshold="max", sort=TRUE)
> # Rotated loadings from Factor Analysis (varimax-rotation)
> 
> Variable     |  ML4  | ML1  | ML3  |  ML2  |  ML7  | ML5  | ML6  | Complexity | Uniqueness
> ------------------------------------------------------------------------------------------
> SD1SD2       | 0.86  |      |      |       |       |      |      |    1.28    |    0.17   
> DFA          | -0.79 |      |      |       |       |      |      |    1.24    |    0.31   
> CSI          | -0.77 |      |      |       |       |      |      |    2.06    |    0.06   
> LnHF         | 0.72  |      |      |       |       |      |      |    2.22    |    0.20   
> HFn          | 0.66  |      |      |       |       |      |      |    1.47    |    0.46   
> CSI_Modified | -0.64 |      |      |       |       |      |      |    3.47    |    0.12   
> LFn          | -0.62 |      |      |       |       |      |      |    1.67    |    0.49   
> C1a          | -0.56 |      |      |       |       |      |      |    1.71    |    0.57   
> HF           | 0.55  |      |      |       |       |      |      |    1.22    |    0.67   
> VHF          | 0.54  |      |      |       |       |      |      |    1.47    |    0.65   
> HTI          | 0.35  |      |      |       |       |      |      |    2.12    |    0.78   
> MCVNN        |       | 0.97 |      |       |       |      |      |    1.12    |  4.99e-03 
> MadNN        |       | 0.96 |      |       |       |      |      |    1.06    |    0.05   
> IQRNN        |       | 0.81 |      |       |       |      |      |    1.15    |    0.29   
> pNN50        |       | 0.65 |      |       |       |      |      |    3.08    |    0.11   
> CVI          |       | 0.61 |      |       |       |      |      |    3.93    |    0.02   
> pNN20        |       | 0.55 |      |       |       |      |      |    3.90    |    0.05   
> S            |       |      | 0.99 |       |       |      |      |    1.00    |    0.02   
> TINN         |       |      | 0.97 |       |       |      |      |    1.03    |    0.04   
> RMSSD        |       |      | 0.92 |       |       |      |      |    1.30    |    0.03   
> LFHF         |       |      | 0.79 |       |       |      |      |    1.45    |    0.25   
> PIP          |       |      |      | 0.99  |       |      |      |    1.02    |  4.87e-03 
> PSS          |       |      |      | 0.94  |       |      |      |    1.03    |    0.11   
> PAS          |       |      |      | 0.79  |       |      |      |    1.46    |    0.24   
> LF           |       |      |      | -0.40 |       |      |      |    2.79    |    0.68   
> RCMSE        |       |      |      |       | -0.67 |      |      |    2.49    |    0.21   
> CMSE         |       |      |      |       | -0.67 |      |      |    2.38    |    0.26   
> C2a          |       |      |      |       | 0.58  |      |      |    1.91    |    0.49   
> AI           |       |      |      |       | -0.57 |      |      |    2.48    |    0.31   
> PI           |       |      |      |       | 0.54  |      |      |    1.68    |    0.62   
> Ca           |       |      |      |       | 0.51  |      |      |    1.51    |    0.67   
> SampEn       |       |      |      |       |       | 0.74 |      |    2.38    |    0.09   
> ApEn         |       |      |      |       |       | 0.73 |      |    1.88    |    0.24   
> CorrDim      |       |      |      |       |       | 0.68 |      |    2.22    |    0.27   
> MSE          |       |      |      |       |       | 0.41 |      |    2.00    |    0.75   
> MeanNN       |       |      |      |       |       |      | 0.64 |    2.25    |    0.37   
> 
> The 7 latent factors (varimax rotation) accounted for 70.43% of the total variance of the original data (ML4 = 17.93%, ML1 = 10.90%, ML3 = 10.87%, ML2 = 10.17%, ML7 = 9.55%, ML5 = 7.45%, ML6 = 3.57%).
plot(efa) +
  see::theme_modern()

Confirmatory Factor Analysis (CFA)

library(lavaan)

model <- parameters::efa_to_cfa(efa, threshold = "max")
cfa <- lavaan::cfa(model, data=data) %>%
  parameters::parameters(standardize=TRUE)
> Error in if (ncol(S) == 1L) { : argument is of length zero
cfa
To Operator From Coefficient SE CI_low CI_high p Type
1 ML4 =~ HTI 0.91 0 Loading
2 ML4 =~ HF -1.00 0 Loading
3 ML4 =~ VHF -1.00 0 Loading
4 ML4 =~ LFn -1.00 0 Loading
5 ML4 =~ HFn -1.00 0 Loading
6 ML4 =~ LnHF -1.00 0 Loading
7 ML4 =~ SD1SD2 -1.00 0 Loading
8 ML4 =~ CSI -1.00 0 Loading
9 ML4 =~ CSI_Modified 1.00 0 Loading
10 ML4 =~ C1a -1.00 0 Loading
11 ML4 =~ DFA -1.00 0 Loading
12 ML1 =~ MadNN 0.96 0 Loading
13 ML1 =~ MCVNN -1.00 0 Loading
14 ML1 =~ IQRNN -1.00 0 Loading
15 ML1 =~ pNN50 -1.00 0 Loading
16 ML1 =~ pNN20 -1.00 0 Loading
17 ML1 =~ CVI -1.00 0 Loading
18 ML3 =~ RMSSD 0.72 0 Loading
19 ML3 =~ TINN 0.86 0 Loading
20 ML3 =~ LFHF 0.00 0 Loading
21 ML3 =~ S 1.00 0 Loading
22 ML2 =~ LF 0.83 0 Loading
23 ML2 =~ PIP -1.00 0 Loading
24 ML2 =~ PSS -1.00 0 Loading
25 ML2 =~ PAS -1.00 0 Loading
26 ML7 =~ AI 0.85 0 Loading
27 ML7 =~ PI 1.00 0 Loading
28 ML7 =~ C2a 1.00 0 Loading
29 ML7 =~ Ca 1.00 0 Loading
30 ML7 =~ CMSE 1.00 0 Loading
31 ML7 =~ RCMSE 1.00 0 Loading
32 ML5 =~ ApEn 0.80 0 Loading
33 ML5 =~ SampEn -0.90 0 Loading
34 ML5 =~ MSE 0.82 0 Loading
35 ML5 =~ CorrDim -0.06 0 Loading
36 ML6 =~ MeanNN 1.00 0 Loading
80 ML4 ~~ ML1 0.39 0 Correlation
81 ML4 ~~ ML3 -0.07 0 Correlation
82 ML4 ~~ ML2 0.76 0 Correlation
83 ML4 ~~ ML7 0.39 0 Correlation
84 ML4 ~~ ML5 -0.66 0 Correlation
85 ML4 ~~ ML6 -0.06 0 Correlation
86 ML1 ~~ ML3 -0.03 0 Correlation
87 ML1 ~~ ML2 0.46 0 Correlation
88 ML1 ~~ ML7 0.48 0 Correlation
89 ML1 ~~ ML5 -0.89 0 Correlation
90 ML1 ~~ ML6 -0.47 0 Correlation
91 ML3 ~~ ML2 -0.07 0 Correlation
92 ML3 ~~ ML7 -0.01 0 Correlation
93 ML3 ~~ ML5 0.05 0 Correlation
94 ML3 ~~ ML6 -0.01 0 Correlation
95 ML2 ~~ ML7 0.33 0 Correlation
96 ML2 ~~ ML5 -0.64 0 Correlation
97 ML2 ~~ ML6 0.04 0 Correlation
98 ML7 ~~ ML5 -0.42 0 Correlation
99 ML7 ~~ ML6 -0.32 0 Correlation
100 ML5 ~~ ML6 0.42 0 Correlation
plot(cfa)

Cluster Analysis

How many clusters

dat <- effectsize::standardize(data[sapply(data, is.numeric)])

n <- parameters::n_clusters(t(dat), package = c("mclust", "cluster"))

n
n_Clusters Method Package
1 Tibs2001SEmax cluster
5 Mixture mclust
plot(n) +
  theme_modern()

library(dendextend)

dat <- effectsize::standardize(data[sapply(data, is.numeric)])

result <- pvclust::pvclust(dat, method.dist="euclidean", method.hclust="ward.D2", nboot=10, quiet=TRUE)

result %>% 
  as.dendrogram() %>% 
  sort() %>% 
  dendextend::pvclust_show_signif_gradient(result, signif_col_fun = grDevices::colorRampPalette(c("black", "red"))) %>% 
  dendextend::pvclust_show_signif(result, signif_value = c(2, 1)) %>%
  dendextend::as.ggdend() %>% 
  ggplot2::ggplot(horiz=TRUE, offset_labels = -1)

References

LS0tDQp0aXRsZTogJyoqQ29uY2VwdHVhbCB2cy4gT2JzZXJ2ZWQgU3RydWN0dXJlIG9mIEhSViBpbmRpY2VzKionDQpzdWJ0aXRsZTogIkRhdGEgQW5hbGF5c2lzIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRoZW1lOiBjZXJ1bGVhbg0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIGRmX3ByaW50OiBrYWJsZQ0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICB3b3JkX2RvY3VtZW50Og0KICAgIHJlZmVyZW5jZV9kb2N4OiB1dGlscy9UZW1wbGF0ZV9Xb3JkLmRvY3gNCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzDQogICAgdG9jOiBubw0KICAgIHRvY19kZXB0aDogMw0KICAgIGRmX3ByaW50OiBrYWJsZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogIHJtYXJrZG93bjo6aHRtbF92aWduZXR0ZToNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMw0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICcyJw0KICAgIGxhdGV4X2VuZ2luZTogeGVsYXRleA0KZWRpdG9yX29wdGlvbnM6DQogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlDQpiaWJsaW9ncmFwaHk6IHV0aWxzL2JpYmxpb2dyYXBoeS5iaWINCmNzbDogdXRpbHMvYXBhLmNzbA0KLS0tDQoNCg0KPCEtLSANCiEhISEgSU1QT1JUQU5UOiBydW4gYHNvdXJjZSgidXRpbHMvcmVuZGVyLlIiKWAgdG8gcHVibGlzaCBpbnN0ZWFkIG9mIGNsaWNraW5nIG9uICdLbml0Jw0KLS0+DQoNCmBgYHtyIHNldHVwLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPVRSVUUsIGluY2x1ZGU9RkFMU0V9DQojIFNldCB1cCB0aGUgZW52aXJvbm1lbnQgKG9yIHVzZSBsb2NhbCBhbHRlcm5hdGl2ZSBgc291cmNlKCJ1dGlscy9jb25maWcuUiIpYCkNCnNvdXJjZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1JlYWxpdHlCZW5kaW5nL1RlbXBsYXRlUmVzdWx0cy9tYWluL3V0aWxzL2NvbmZpZy5SIikgIA0KDQpmYXN0IDwtIEZBTFNFICAjIE1ha2UgdGhpcyBmYWxzZSB0byBza2lwIHRoZSBjaHVua3MNCg0KIyBTZXQgdGhlbWUNCmdncGxvdDI6OnRoZW1lX3NldChzZWU6OnRoZW1lX21vZGVybigpKQ0KDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24NCg0KYGBge3IgYmFkZ2VzLCBlY2hvPUZBTFNFLCBtZXNzYWdlPVRSVUUsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2FzaXMnfQ0KIyBUaGlzIGNodW5rIGlzIGEgYml0IGNvbXBsZXggc28gZG9uJ3Qgd29ycnkgYWJvdXQgaXQ6IGl0J3MgbWFkZSB0byBhZGQgYmFkZ2VzIHRvIHRoZSBIVE1MIHZlcnNpb25zDQojIE5PVEU6IFlvdSBoYXZlIHRvIHJlcGxhY2UgdGhlIGxpbmtzIGFjY29yZGluZ2x5IHRvIGhhdmUgd29ya2luZyAiYnV0dG9ucyIgb24gdGhlIEhUTUwgdmVyc2lvbnMNCmlmICgha25pdHI6OmlzX2xhdGV4X291dHB1dCgpICYmIGtuaXRyOjppc19odG1sX291dHB1dCgpKSB7DQogIGNhdCgiIVtCdWlsZF0oaHR0cHM6Ly9naXRodWIuY29tL1RhbS1QaGFtL0hSVlN0cnVjdHVyZS93b3JrZmxvd3MvQnVpbGQvYmFkZ2Uuc3ZnKQ0KICAgICAgWyFbV2Vic2l0ZV0oaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9iYWRnZS9yZXBvLVJlYWRtZS0yMTk2RjMpXShodHRwczovL2dpdGh1Yi5jb20vVGFtLVBoYW0vSFJWU3RydWN0dXJlKQ0KICAgICAgWyFbV2Vic2l0ZV0oaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9iYWRnZS92aXNpdC13ZWJzaXRlLUU5MUU2MyldKGh0dHBzOi8vcmVhbGl0eWJlbmRpbmcuZ2l0aHViLmlvL1RlbXBsYXRlUmVzdWx0cy8pDQogICAgICBbIVtXZWJzaXRlXShodHRwczovL2ltZy5zaGllbGRzLmlvL2JhZGdlL2Rvd25sb2FkLS5kb2N4LUZGNTcyMildKGh0dHBzOi8vZ2l0aHViLmNvbS9SZWFsaXR5QmVuZGluZy9UZW1wbGF0ZVJlc3VsdHMvcmF3L21haW4vd29yZF9hbmRfcGRmL1N1cHBsZW1lbnRhcnlNYXRlcmlhbHMuZG9jeCkNCiAgICAgIFshW1dlYnNpdGVdKGh0dHBzOi8vaW1nLnNoaWVsZHMuaW8vYmFkZ2Uvc2VlLS5wZGYtRkY5ODAwKV0oaHR0cHM6Ly9naXRodWIuY29tL1JlYWxpdHlCZW5kaW5nL1RlbXBsYXRlUmVzdWx0cy9ibG9iL21haW4vd29yZF9hbmRfcGRmL1N1cHBsZW1lbnRhcnlNYXRlcmlhbHMucGRmKSIpDQp9DQpgYGANCg0KIyMgSW50cm9kdWN0aW9uDQoNClRoZSBhaW0gb2YgdGhpcyBzdHVkeSBpcyB0byBleHBsb3JlIHRoZSBmYWN0b3Igc3RydWN0dXJlIG9mIEhSViBpbmRpY2VzLg0KDQoNCiMjIERhdGFiYXNlcw0KDQojIyMgR2xhc2dvdyBVbml2ZXJzaXR5IERhdGFiYXNlDQoNClRoZSBHVURCIERhdGFiYXNlIFtAaG93ZWxsMjAxOGhpZ2hdIGNvbnRhaW5zIEVDR3MgZnJvbSAyNSBzdWJqZWN0cy4gRWFjaCBzdWJqZWN0IHdhcyByZWNvcmRlZCBwZXJmb3JtaW5nIDUgZGlmZmVyZW50IHRhc2tzIGZvciB0d28gbWludXRlcyAoc2l0dGluZywgZG9pbmcgYSBtYXRocyB0ZXN0IG9uIGEgdGFibGV0LCB3YWxraW5nIG9uIGEgdHJlYWRtaWxsLCBydW5uaW5nIG9uIGEgdHJlYWRtaWxsLCB1c2luZyBhIGhhbmQgYmlrZSkuIFRoZSBzYW1wbGluZyByYXRlIGlzIDI1MEh6IGZvciBhbGwgdGhlIGNvbmRpdGlvbnMuDQoNClRoZSBzY3JpcHQgdG8gZG93bmxvYWQgYW5kIGZvcm1hdCB0aGUgZGF0YWJhc2UgdXNpbmcgdGhlIFsqKkVDRy1HVURCKipdKGh0dHBzOi8vZ2l0aHViLmNvbS9iZXJuZHBvcnIvRUNHLUdVREIpIFB5dGhvbiBwYWNrYWdlIGJ5IEJlcm5kIFBvcnIgY2FuIGJlIGZvdW5kIFsqKmhlcmUqKl0oaHR0cHM6Ly9naXRodWIuY29tL25ldXJvcHN5Y2hvbG9neS9OZXVyb0tpdC9ibG9iL2Rldi9kYXRhL2d1ZGIvZG93bmxvYWRfZ3VkYi5weSkuDQoNCiMjIyBNSVQtQklIIEFycmh5dGhtaWEgRGF0YWJhc2UNCg0KVGhlIE1JVC1CSUggQXJyaHl0aG1pYSBEYXRhYmFzZSBbTUlULUFycmh5dGhtaWE7IEBtb29keTIwMDFpbXBhY3RdIGNvbnRhaW5zIDQ4IGV4Y2VycHRzIG9mIDMwLW1pbiBvZiB0d28tY2hhbm5lbCBhbWJ1bGF0b3J5IEVDRyByZWNvcmRpbmdzIHNhbXBsZWQgYXQgMzYwSHogYW5kIDI1IGFkZGl0aW9uYWwgcmVjb3JkaW5ncyBmcm9tIHRoZSBzYW1lIHBhcnRpY2lwYW50cyBpbmNsdWRpbmcgY29tbW9uIGJ1dCBjbGluaWNhbGx5IHNpZ25pZmljYW50IGFycmh5dGhtaWFzIChkZW5vdGVkIGFzIHRoZSBgTUlULUFycmh5dGhtaWEteGAgZGF0YWJhc2UpLg0KDQpUaGUgc2NyaXB0IHRvIGRvd25sb2FkIGFuZCBmb3JtYXQgdGhlIGRhdGFiYXNlIHVzaW5nIHRoZSBjYW4gYmUgZm91bmQgWyoqaGVyZSoqXShodHRwczovL2dpdGh1Yi5jb20vbmV1cm9wc3ljaG9sb2d5L05ldXJvS2l0L2Jsb2IvZGV2L2RhdGEvbWl0X2Fycmh5dGhtaWEvZG93bmxvYWRfbWl0X2Fycmh5dGhtaWEucHkpLg0KDQoNCiMjIyBNSVQtQklIIE5vcm1hbCBTaW51cyBSaHl0aG0gRGF0YWJhc2UNCg0KVGhpcyBkYXRhYmFzZSBpbmNsdWRlcyAxOCBjbGVhbiBsb25nLXRlcm0gRUNHIHJlY29yZGluZ3Mgb2Ygc3ViamVjdHMuIER1ZSB0byBtZW1vcnkgbGltaXRzLCB3ZSBvbmx5IGtlcHQgdGhlIHNlY29uZCBob3VyIG9mIHJlY29yZGluZyBvZiBlYWNoIHBhcnRpY2lwYW50Lg0KDQpUaGUgc2NyaXB0IHRvIGRvd25sb2FkIGFuZCBmb3JtYXQgdGhlIGRhdGFiYXNlIHVzaW5nIHRoZSBjYW4gYmUgZm91bmQgWyoqaGVyZSoqXShodHRwczovL2dpdGh1Yi5jb20vbmV1cm9wc3ljaG9sb2d5L05ldXJvS2l0L2Jsb2IvZGV2L2RhdGEvbWl0X25vcm1hbC9kb3dubG9hZF9taXRfbm9ybWFsLnB5KS4NCg0KDQo8IS0tICMjIyBMb2JhY2hldnNreSBVbml2ZXJzaXR5IEVsZWN0cm9jYXJkaW9ncmFwaHkgRGF0YWJhc2UgLS0+DQoNCjwhLS0gVGhlIExvYmFjaGV2c2t5IFVuaXZlcnNpdHkgRWxlY3Ryb2NhcmRpb2dyYXBoeSBEYXRhYmFzZSBbTFVEQjsgQGthbHlha3VsaW5hMjAxOGx1XSBjb25zaXN0cyBvZiAyMDAgMTAtc2Vjb25kIDEyLWxlYWQgRUNHIHNpZ25hbCByZWNvcmRzIHJlcHJlc2VudGluZyBkaWZmZXJlbnQgbW9ycGhvbG9naWVzIG9mIHRoZSBFQ0cgc2lnbmFsLiBUaGUgRUNHcyB3ZXJlIGNvbGxlY3RlZCBmcm9tIGhlYWx0aHkgdm9sdW50ZWVycyBhbmQgcGF0aWVudHMsIHdoaWNoIGhhZCB2YXJpb3VzIGNhcmRpb3Zhc2N1bGFyIGRpc2Vhc2VzLiBUaGUgYm91bmRhcmllcyBvZiBQLCBUIHdhdmVzIGFuZCBRUlMgY29tcGxleGVzIHdlcmUgbWFudWFsbHkgYW5ub3RhdGVkIGJ5IGNhcmRpb2xvZ2lzdHMgZm9yIGFsbCAyMDAgcmVjb3Jkcy4gLS0+DQoNCiMjIyBGYW50YXNpYSBEYXRhYmFzZQ0KDQpUaGUgRmFudGFzaWEgZGF0YWJhc2UgW0BpeWVuZ2FyMTk5NmFnZV0gY29uc2lzdHMgb2YgdHdlbnR5IHlvdW5nIGFuZCB0d2VudHkgZWxkZXJseSBoZWFsdGh5IHN1YmplY3RzLiBBbGwgc3ViamVjdHMgcmVtYWluZWQgaW4gYSByZXN0aW5nIHN0YXRlIGluIHNpbnVzIHJoeXRobSB3aGlsZSB3YXRjaGluZyB0aGUgbW92aWUgRmFudGFzaWEgKERpc25leSwgMTk0MCkgdG8gaGVscCBtYWludGFpbiB3YWtlZnVsbmVzcy4gVGhlIGNvbnRpbnVvdXMgRUNHIHNpZ25hbHMgd2VyZSBkaWdpdGl6ZWQgYXQgMjUwIEh6LiBFYWNoIGhlYXJ0YmVhdCB3YXMgYW5ub3RhdGVkIHVzaW5nIGFuIGF1dG9tYXRlZCBhcnJoeXRobWlhIGRldGVjdGlvbiBhbGdvcml0aG0sIGFuZCBlYWNoIGJlYXQgYW5ub3RhdGlvbiB3YXMgdmVyaWZpZWQgYnkgdmlzdWFsIGluc3BlY3Rpb24uDQoNCg0KDQojIyBQcm9jZWR1cmUNCg0KYGBge3B5dGhvbiwgZXZhbD1GQUxTRSwgZWNobz1GQUxTRX0NCmltcG9ydCBwYW5kYXMgYXMgcGQNCmltcG9ydCBudW1weSBhcyBucA0KaW1wb3J0IG5ldXJva2l0MiBhcyBuaw0KDQojIExvYWQgVHJ1ZSBSLXBlYWtzIGxvY2F0aW9uDQpkYXRhZmlsZXMgPSBbcGQucmVhZF9jc3YoIi4uLy4uL2RhdGEvZ3VkYi9ScGVha3MuY3N2IiksDQogICAgICAgICAgICAgcGQucmVhZF9jc3YoIi4uLy4uL2RhdGEvbWl0X2Fycmh5dGhtaWEvUnBlYWtzLmNzdiIpLA0KICAgICAgICAgICAgIHBkLnJlYWRfY3N2KCIuLi8uLi9kYXRhL21pdF9ub3JtYWwvUnBlYWtzLmNzdiIpLA0KICAgICAgICAgICAgIHBkLnJlYWRfY3N2KCIuLi8uLi9kYXRhL2ZhbnRhc2lhL1JwZWFrcy5jc3YiKV0NCg0KIyBHZXQgcmVzdWx0cw0KYWxsX3Jlc3VsdHMgPSBwZC5EYXRhRnJhbWUoKQ0KDQpmb3IgZmlsZSBpbiBkYXRhZmlsZXM6DQogICAgZm9yIGRhdGFiYXNlIGluIG5wLnVuaXF1ZShmaWxlWyJEYXRhYmFzZSJdKToNCiAgICAgICAgZGF0YSA9IGZpbGVbZmlsZVsiRGF0YWJhc2UiXSA9PSBkYXRhYmFzZV0NCiAgICAgICAgZm9yIHBhcnRpY2lwYW50IGluIG5wLnVuaXF1ZShkYXRhWyJQYXJ0aWNpcGFudCJdKToNCiAgICAgICAgICAgIGRhdGFfcGFydGljaXBhbnQgPSBkYXRhW2RhdGFbIlBhcnRpY2lwYW50Il0gPT0gcGFydGljaXBhbnRdDQogICAgICAgICAgICBzYW1wbGluZ19yYXRlID0gbnAudW5pcXVlKGRhdGFfcGFydGljaXBhbnRbIlNhbXBsaW5nX1JhdGUiXSlbMF0NCiAgICAgICAgICAgIHJwZWFrcyA9IGRhdGFfcGFydGljaXBhbnRbIlJwZWFrcyJdLnZhbHVlcw0KDQogICAgICAgICAgICByZXN1bHRzID0gbmsuaHJ2KHJwZWFrcywgc2FtcGxpbmdfcmF0ZT1zYW1wbGluZ19yYXRlKQ0KICAgICAgICAgICAgcmVzdWx0c1siUGFydGljaXBhbnQiXSA9IHBhcnRpY2lwYW50DQogICAgICAgICAgICByZXN1bHRzWyJEYXRhYmFzZSJdID0gZGF0YWJhc2UNCiAgICAgICAgICAgIHJlc3VsdHNbIlJlY29yZGluZ19MZW5ndGgiXSA9IHJwZWFrc1stMV0gLyBzYW1wbGluZ19yYXRlIC8gNjANCg0KICAgICAgICAgICAgYWxsX3Jlc3VsdHMgPSBwZC5jb25jYXQoW2FsbF9yZXN1bHRzLCByZXN1bHRzXSwgYXhpcz0wKQ0KDQphbGxfcmVzdWx0cy50b19jc3YoImRhdGEvZGF0YS5jc3YiLCBpbmRleD1GYWxzZSkNCmBgYA0KDQojIyBSZXN1bHRzDQoNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGVhc3lzdGF0cykNCg0KZGF0YSA8LSByZWFkLmNzdigiZGF0YS9kYXRhLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkgJT4lIA0KICBzZWxlY3QoLUhSVl9VTEYsIC1IUlZfVkxGKSAlPiUgICMgRW1wdHkNCiAgZmlsdGVyKERhdGFiYXNlICE9ICJMVURCIikgIyB0b28gc2hvcnQgcmVjb3JkaW5ncywgbWFueSBpbmRpY2VzIGRpZG4ndCBjb252ZXJnZQ0KbmFtZXMoZGF0YSkgPC0gc3RyaW5ncjo6c3RyX3JlbW92ZShuYW1lcyhkYXRhKSwgIkhSVl8iKQ0KYGBgDQoNCg0KIyMjIFJlZHVuZGFudCBJbmRpY2VzDQoNCiMjIyMgUmVtb3ZlIEVxdWl2YWxlbnQgKHIgaGlnaGVyIHRoYW4gLjk5NSkNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xNywgZmlnLmhlaWdodD0xN30NCmRhdGEgJT4lIA0KICBjb3JyZWxhdGlvbjo6Y29ycmVsYXRpb24oKSAlPiUgDQogIGZpbHRlcihhYnMocikgPiAwLjk5NSkgJT4lIA0KICBhcnJhbmdlKFBhcmFtZXRlcjEsIGRlc2MoYWJzKHIpKSkNCg0KZGF0YSA8LSBkYXRhICU+JSANCiAgc2VsZWN0KC1TRFNELCAtU0QxLCAtU0QxZCwgLVNEMWEsIC1DVlNEKSAlPiUgICMgU2FtZSBhcyBSTVNTRCANCiAgc2VsZWN0KC1TRE5OZCwgLVNETk5hKSAlPiUgICMgU2FtZSBhcyBTRE5ODQogIHNlbGVjdCgtU0QyZCwgLVNEMmEpICU+JSAgICMgU2FtZSBhcyBTRDINCiAgc2VsZWN0KC1DZCkgJT4lICAgIyBTYW1lIGFzIENhDQogIHNlbGVjdCgtQzFkLCAtQzJkKSAjIFNhbWUgYXMgQzFhIGFuZCBDMmENCmBgYA0KDQojIyMjIFJlbW92ZSBTdHJvbmdseSBDb3JyZWxhdGVkIChyIGhpZ2hlciB0aGFuIC45OCkNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xNywgZmlnLmhlaWdodD0xN30NCmRhdGEgJT4lIA0KICBjb3JyZWxhdGlvbjo6Y29ycmVsYXRpb24oKSAlPiUgDQogIGZpbHRlcihhYnMocikgPiAwLjk1KSAlPiUNCiAgYXJyYW5nZShQYXJhbWV0ZXIxLCBkZXNjKGFicyhyKSkpDQoNCmRhdGEgPC0gZGF0YSAlPiUgDQogIHNlbGVjdCgtR0ksIC1TSSkgJT4lICAjIFNhbWUgYXMgQUkgDQogIHNlbGVjdCgtU0QyKSAlPiUgICMgU2FtZSBhcyBTRE5ODQogIHNlbGVjdCgtTWVkaWFuTk4pICU+JSAgIyBTYW1lIGFzIE1lYW5OTg0KICBzZWxlY3QoLUlBTFMpICU+JSAgIyBTYW1lIGFzIFBJUA0KICBzZWxlY3QoLVNETk4sIC1DVk5OKSAjIFNhbWUgYXMgUk1TU0QNCmBgYA0KDQoNCg0KIyMjIFJlY29yZGluZyBMZW5ndGgNCg0KIyMjIyBJbnZlc3RpZ2F0ZSBlZmZlY3QNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KY29ycmVsYXRpb24oZGF0YSkgJT4lIA0KICBmaWx0ZXIoUGFyYW1ldGVyMiA9PSAiUmVjb3JkaW5nX0xlbmd0aCIpICU+JSANCiAgYXJyYW5nZShkZXNjKGFicyhyKSkpDQpgYGANCg0KIyMjIyBBZGp1c3QgdGhlIGRhdGEgZm9yIHJlY29yZGluZyBsZW5ndGgNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KZGF0YSA8LSBlZmZlY3RzaXplOjphZGp1c3QoZGF0YSwgZWZmZWN0PSJSZWNvcmRpbmdfTGVuZ3RoIikgJT4lIA0KICBzZWxlY3QoLVJlY29yZGluZ19MZW5ndGgpDQpgYGANCg0KIyMjIEdhdXNzaWFuIEdyYXBoaWNhbCBNb2RlbA0KDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9MTcsIGZpZy5oZWlnaHQ9MTd9DQpsaWJyYXJ5KGdncmFwaCkNCg0KZGF0YSAlPiUgDQogIGNvcnJlbGF0aW9uOjpjb3JyZWxhdGlvbihwYXJ0aWFsPUZBTFNFKSAlPiUgDQogIGNvcnJlbGF0aW9uOjpjb3JfdG9fcGNvcigpICU+JSANCiAgZmlsdGVyKGFicyhyKSA+IDAuMikgJT4lDQogIHRpZHlncmFwaDo6YXNfdGJsX2dyYXBoKGRpcmVjdGVkPUZBTFNFKSAlPiUgDQogIGRwbHlyOjptdXRhdGUoY2xvc2VuZXNzID0gdGlkeWdyYXBoOjpjZW50cmFsaXR5X2Nsb3NlbmVzcyhub3JtYWxpemVkID0gVFJVRSksDQogICAgICAgICAgICAgICAgZGVncmVlID0gdGlkeWdyYXBoOjpjZW50cmFsaXR5X2RlZ3JlZShub3JtYWxpemVkID0gVFJVRSksDQogICAgICAgICAgICAgICAgYmV0d2VlbmVzcyA9IHRpZHlncmFwaDo6Y2VudHJhbGl0eV9iZXR3ZWVubmVzcyhub3JtYWxpemVkID0gVFJVRSkpICU+JQ0KICB0aWR5Z3JhcGg6OmFjdGl2YXRlKG5vZGVzKSAlPiUNCiAgZHBseXI6Om11dGF0ZShncm91cDEgPSBhcy5mYWN0b3IodGlkeWdyYXBoOjpncm91cF9lZGdlX2JldHdlZW5uZXNzKCkpLA0KICAgICAgICAgICAgICAgICMgZ3JvdXAyID0gYXMuZmFjdG9yKHRpZHlncmFwaDo6Z3JvdXBfb3B0aW1hbCgpKSwNCiAgICAgICAgICAgICAgICAjIGdyb3VwMyA9IGFzLmZhY3Rvcih0aWR5Z3JhcGg6Omdyb3VwX3dhbGt0cmFwKCkpLA0KICAgICAgICAgICAgICAgICMgZ3JvdXA0ID0gYXMuZmFjdG9yKHRpZHlncmFwaDo6Z3JvdXBfc3BpbmdsYXNzKCkpLA0KICAgICAgICAgICAgICAgIGdyb3VwNSA9IGFzLmZhY3Rvcih0aWR5Z3JhcGg6Omdyb3VwX2xvdXZhaW4oKSkpICU+JSANCiAgZ2dyYXBoOjpnZ3JhcGgobGF5b3V0ID0gImZyIikgKw0KICAgIGdncmFwaDo6Z2VvbV9lZGdlX2FyYyhhZXMoY29sb3VyID0gciwgZWRnZV93aWR0aCA9IGFicyhyKSksIHN0cmVuZ3RoID0gMC4xLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogICAgZ2dyYXBoOjpnZW9tX25vZGVfcG9pbnQoYWVzKHNpemUgPSBkZWdyZWUsIGNvbG9yID0gZ3JvdXA1KSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICAgIGdncmFwaDo6Z2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIGNvbG91ciA9ICJ3aGl0ZSIpICsNCiAgICBnZ3JhcGg6OnNjYWxlX2VkZ2VfY29sb3JfZ3JhZGllbnQyKGxvdyA9ICIjYTIwMDI1IiwgaGlnaCA9ICIjMDA4YTAwIiwgbmFtZSA9ICJyIikgKw0KICAgIGdncmFwaDo6dGhlbWVfZ3JhcGgoKSArDQogICAgZ3VpZGVzKGVkZ2Vfd2lkdGggPSBGQUxTRSkgKw0KICAgIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBleHBhbnNpb24oYyguMTAsIC4xMCkpKSArDQogICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGV4cGFuc2lvbihjKC4xMCwgLjEwKSkpICsNCiAgICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDIwLCAzMCkpICsNCiAgICBzY2FsZV9lZGdlX3dpZHRoX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDAuNSwgMikpICsNCiAgICBzZWU6OnNjYWxlX2NvbG9yX21hdGVyaWFsX2QocGFsZXR0ZT0icmFpbmJvdyIsIHJldmVyc2U9VFJVRSkNCmBgYA0KDQpHcm91cHMgd2VyZSBpZGVudGlmaWVkIHVzaW5nIHRoZSBbdGlkeWdyYXBoOjpncm91cF9vcHRpbWFsXShodHRwczovL3JkcnIuaW8vY3Jhbi90aWR5Z3JhcGgvbWFuL2dyb3VwX2dyYXBoLmh0bWwpIGFsZ29yaXRobS4NCg0KDQojIyMgRmFjdG9yIEFuYWx5c2lzDQoNCg0KIyMjIyBIb3cgbWFueSBmYWN0b3JzIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmNvciA8LSBjb3JyZWxhdGlvbjo6Y29ycmVsYXRpb24oZGF0YVtzYXBwbHkoZGF0YSwgaXMubnVtZXJpYyldKSAlPiUgDQogIGFzLm1hdHJpeCgpDQoNCm4gPC0gcGFyYW1ldGVyczo6bl9mYWN0b3JzKGRhdGEsIGNvcj1jb3IpDQoNCm4NCg0KcGxvdChuKSArDQogIHNlZTo6dGhlbWVfbW9kZXJuKCkNCmBgYA0KDQojIyMjIEV4cGxvcmF0b3J5IEZhY3RvciBBbmFseXNpcyAoRUZBKQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTE1fQ0KZWZhIDwtIHBhcmFtZXRlcnM6OmZhY3Rvcl9hbmFseXNpcyhkYXRhLCBjb3I9Y29yLCBuPTcsIHJvdGF0aW9uPSJ2YXJpbWF4IiwgZm09Im1sIikNCg0KcHJpbnQoZWZhLCB0aHJlc2hvbGQ9Im1heCIsIHNvcnQ9VFJVRSkNCg0KcGxvdChlZmEpICsNCiAgc2VlOjp0aGVtZV9tb2Rlcm4oKQ0KYGBgDQoNCiMjIyMgQ29uZmlybWF0b3J5IEZhY3RvciBBbmFseXNpcyAoQ0ZBKQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTE1fQ0KbGlicmFyeShsYXZhYW4pDQoNCm1vZGVsIDwtIHBhcmFtZXRlcnM6OmVmYV90b19jZmEoZWZhLCB0aHJlc2hvbGQgPSAibWF4IikNCmNmYSA8LSBsYXZhYW46OmNmYShtb2RlbCwgZGF0YT1kYXRhKSAlPiUNCiAgcGFyYW1ldGVyczo6cGFyYW1ldGVycyhzdGFuZGFyZGl6ZT1UUlVFKQ0KDQpjZmENCg0KcGxvdChjZmEpDQpgYGANCg0KDQojIyMgQ2x1c3RlciBBbmFseXNpcw0KDQojIyMjIEhvdyBtYW55IGNsdXN0ZXJzDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGF0IDwtIGVmZmVjdHNpemU6OnN0YW5kYXJkaXplKGRhdGFbc2FwcGx5KGRhdGEsIGlzLm51bWVyaWMpXSkNCg0KbiA8LSBwYXJhbWV0ZXJzOjpuX2NsdXN0ZXJzKHQoZGF0KSwgcGFja2FnZSA9IGMoIm1jbHVzdCIsICJjbHVzdGVyIikpDQoNCm4NCg0KcGxvdChuKSArDQogIHRoZW1lX21vZGVybigpDQpgYGANCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9MTB9DQpsaWJyYXJ5KGRlbmRleHRlbmQpDQoNCmRhdCA8LSBlZmZlY3RzaXplOjpzdGFuZGFyZGl6ZShkYXRhW3NhcHBseShkYXRhLCBpcy5udW1lcmljKV0pDQoNCnJlc3VsdCA8LSBwdmNsdXN0OjpwdmNsdXN0KGRhdCwgbWV0aG9kLmRpc3Q9ImV1Y2xpZGVhbiIsIG1ldGhvZC5oY2x1c3Q9IndhcmQuRDIiLCBuYm9vdD0xMCwgcXVpZXQ9VFJVRSkNCg0KcmVzdWx0ICU+JSANCiAgYXMuZGVuZHJvZ3JhbSgpICU+JSANCiAgc29ydCgpICU+JSANCiAgZGVuZGV4dGVuZDo6cHZjbHVzdF9zaG93X3NpZ25pZl9ncmFkaWVudChyZXN1bHQsIHNpZ25pZl9jb2xfZnVuID0gZ3JEZXZpY2VzOjpjb2xvclJhbXBQYWxldHRlKGMoImJsYWNrIiwgInJlZCIpKSkgJT4lIA0KICBkZW5kZXh0ZW5kOjpwdmNsdXN0X3Nob3dfc2lnbmlmKHJlc3VsdCwgc2lnbmlmX3ZhbHVlID0gYygyLCAxKSkgJT4lDQogIGRlbmRleHRlbmQ6OmFzLmdnZGVuZCgpICU+JSANCiAgZ2dwbG90Mjo6Z2dwbG90KGhvcml6PVRSVUUsIG9mZnNldF9sYWJlbHMgPSAtMSkNCmBgYA0KDQoNCg0KIyMgUmVmZXJlbmNlcw0K